
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class Protocol {
	
	private static final String SEPARATOR = ":";

	/**
	 * Methode de parsage du protocol
	 * @param entree le flux d'entree
	 * @param from 1: from joueur    0: from serveur 
	 * @throws Exception
	 */
	public static void parser(BufferedReader entree, Client client) throws IOException, ProtocolException {
		HashMap<String, String> h = new HashMap<String, String>(); 
		String s;
		while((s=entree.readLine())!= null ){ 
			System.out.println(s);
			if (s.equals("FIN")){
				break;
			}
			try{
				Pattern pattern = Pattern.compile("^(\\w*)"+SEPARATOR+"(.*)$");
				Matcher matcher = pattern.matcher(s);
				if (matcher.find()) {
					String action = matcher.group(1);
					String values = matcher.group(2);
					h.put(action, values);
				} else {
					throw new ProtocolException("Non respect du protocol : "+s, h);
				}
			}catch(Exception e){
				System.out.println("Erreur de parsing, ne peut splitter sur ':'");
				e.printStackTrace();
				return;
			}
		}
		protocolHandler(h, client);
	}
	
	/**
	 * Methode qui appelle les differentes fonctions de communication
	 * @param h la hashmap parse par le protocol
	 * @param from 1: from joueur    0: from serveur 
	 * @throws ProtocolException
	 */
	private static void protocolHandler(HashMap<String, String> h, Client c) throws ProtocolException{
		String type = h.get("type");
		
		if (type.equalsIgnoreCase("chat")){
			rcv_chat(h.get("data"), c, h.get("table"),h.get("couleur"));		
			return;
		}
		if(type.equalsIgnoreCase("auth")){
			rcv_otp(h.get("otp"), c);
			return;
		}
	}
	
	/**
	 * On est serveur et on recoit une communication de type chat
	 * @param s les donnee que l'on recoit
	 * @param pseudo le pseudo emetteur de s
	 */
	private static void rcv_chat(String s, Client c,String salle,String couleur){
		Serveur.envoieATous(s, c.name,salle,couleur);
	}

	/**
	 * On est serveur et on envoie une communication de type chat
	 * @param p le printwiter du client
	 * @param s les donnees a envoyer
	 * @param pseudo le pseudo emeteur des donnees
	 * @throws ProtocolException 
	 */
	public static void snd_chat(PrintWriter p,String prefix, String s, String pseudo,String table,String couleur) throws ProtocolException{
		HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "chat");
		hp.put("data", s);
		hp.put("prefix", prefix);
		hp.put("from", pseudo);
		hp.put("table", table);
		hp.put("couleur", couleur);
		formatToProtocol(p, hp);
	}

	/**
	 * Le serveur ajoute un joueur a la liste
	 * @param p
	 * @param pseudo
	 * @param status
	 * @param table
	 * @param couleur
	 * @throws ProtocolException 
	 */
	public static void snd_joueur(PrintWriter p, String pseudo, String status, String couleur) throws ProtocolException{
		HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "joueur");
		hp.put("pseudo", pseudo);
		hp.put("status", status);
		hp.put("couleur", couleur);
		formatToProtocol(p,hp);
	}

	/**
	 * Le serveur dit que ce joueur a le droit de se connecter
	 * @param p
	 * @throws ProtocolException 
	 */
	public static void snd_connectOk(PrintWriter p) throws ProtocolException {
		HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "connect");
		hp.put("status", "ok");
		formatToProtocol(p,hp);
	}
	
	/**
	 * Envoie les donnees dans le format du protocol en verifiant 
	 * qu'elles sont bien formees.
	 * @param p 
	 * @param hp
	 */
	public static void formatToProtocol(PrintWriter p,HashMap<String, String> hp ) throws ProtocolException {
		try{
			hp.get("type");
		}catch(Exception e){
			throw new ProtocolException("Il n'y a pas de 'type' pour cet envoie de donnee");
		}
		for(Entry<String, String> entry : hp.entrySet()) {
		    String cle = entry.getKey();
		    String valeur = entry.getValue();
		    
		    // verif sur la cle et la valeur
		    if(cle.matches(":")){ // la regexp du parser prend le 1er ':' donc il en faut pas ici
		    	throw new ProtocolException("La cle contient ':'");
		    }
		    // Ecriture des donnees
		    p.println(cle+":"+valeur);
		}
		p.println("FIN");
		p.flush();
	}
	
    /**
     * Envoie du challenge au client
     * @param c
     * @throws ProtocolException
     */
    public static void snd_otp(Client c) throws ProtocolException{
    	int sequence = Auth.makeSequence();
    	String seed = Auth.makeSeed();
    	c.sequence = sequence;
    	c.seed = seed;
    	HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "auth");
		hp.put("sequence", ""+sequence);
		hp.put("seed", ""+seed);
		formatToProtocol(c.pw,hp);
    }
    
    /**
     * Reception du challenge resolu par le client
     * @param otp
     * @param c
     */
	private static void rcv_otp(String otp, Client c){
    	try {
    		//Verif dans la base de donne TODO
    		byte[] b = Auth.compute(c.seed,"toto", c.sequence); // FIXME hardcoded
    		String hash = new BigInteger(b).toString(16);
			
    		if (hash.equalsIgnoreCase(otp)){ // Identification OK
				Serveur.envoieATous(c.name+" vient de se connecter", "Serveur","salle",Serveur.couleur);
    			Protocol.snd_connectOk(c.pw);
    			Serveur.updateConnectedJoueurs(c);
    			Serveur.insererJoueur(c);
    		}else{ // Identification NON OK
    			System.err.println("Identification NON OK");
    			try {
					c.th.connexion.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
    		}
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (ProtocolException e) {
			e.printStackTrace();
		}
    }
}
